package cn.itedus.lottery.domain.strategy.service.algorithm;

import cn.itedus.lottery.domain.strategy.model.vo.AwardRateInfo;

import java.util.List;

/**
 * 算法
 * 两种抽奖算法描述，场景A20%、B30%、C50%
 *          总体概率：如果A奖品抽空后，B和C奖品的概率按照 3:5 均分，相当于B奖品中奖概率由 0.3 升为 0.375
 *          单项概率：如果A奖品抽空后，B和C保持目前中奖概率，用户抽奖扔有20%中为A，因A库存抽空则结果展示为未中奖。
 *                   为了运营成本，通常这种情况的使用的比较多
 * 无论任何一种抽奖算法的使用，都以这个接口作为标准的抽奖接口进行抽奖。
 * strategyId 是抽奖策略、excludeAwardIds 排除掉已经不能作为抽奖的奖品ID，留给风控和空库存使用
 */
public interface IDrawAlgorithm {

    /**
     * 程序启动时初始化概率元祖，在初始化完成后使用过程中不允许修改元祖数据
     * <p>
     * 元祖数据作用在于讲百分比内(0.2、0.3、0.5)的数据，转换为一整条数组上分区数据，如下；
     * 0.2 = 0 ~ 0.2
     * 0.3 = 0 + 0.2 ~ 0.2 + 0.3 = 0.2 ~ 0.5
     * 0.5 = 0.5 ~ 1 （计算方式同上）
     * <p>
     * 通过数据拆分为整条后，再根据0-100中各个区间的奖品信息，使用斐波那契散列计算出索引位置，把奖品数据存放到元祖中。比如：
     * <p>
     *     1. 把0.2转换成 20
     *     2. 20 对应的斐波那契哈希值：(20 * HASH_INCREMENT + HASH_INCREMENT) = -1549107828 HASH_INCREMENT = 0x61c88647
     *     3. 再通过哈希值计算索引位置：hashCode & (rateTuple.length - 1) = 12
     *     4. 那么tup[14] = 0.2 中奖概率对应的奖品
     *     5. 当后续通过随机数获取到1-100的值后，可以直接定位对应的奖品信息，通过这样的方式把轮询算奖的时间复杂度从0(n),降到0(1)
     * </p>
     *
     * @param strategyId 策略ID
     * @param strategyMode 抽奖策略模式
     * @param awardRateInfoList 奖品概率配置集合 [值示例：AwardRateInfo.awardRate = 0.04]
     */
    void initRateTuple(Long strategyId, Integer strategyMode, List<AwardRateInfo> awardRateInfoList);

    /**
     * 判断是否已经做了数据初始化
     * @param strategyId 策略ID
     * @return 判断结果
     */
    boolean isExist(Long strategyId);

    /**
     * SecureRandom 生成随机数，索引到对应的奖品信息返回结果
     * @param strategyId 策略ID
     * @param excludeAwardIds 排除掉已经不能作为抽奖的奖品ID，留给风控和空库存使用
     * @return 中奖结果
     */
    String randomDraw(Long strategyId, List<String> excludeAwardIds);
}
